package eventManager;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import controller.IController;
import controls.IMasterControl;

import utilities.Key;

public class EventManager implements IEventManager {
	
	private static final Logger logger = Logger.getLogger(EventManager.class.getName());
	
	Set<String> eventNameSet = new HashSet<String>();
	ArrayList<ArrayList<IEventListener>> eventListenerTable = new ArrayList<ArrayList<IEventListener>>(); //NOTE:  May not be necessary
	HashMap<String, ArrayList<IEventListener>> eventListenerMap = new HashMap<String, ArrayList<IEventListener>>();
	ArrayList<ArrayList<AppEvent>> eventQueue = new ArrayList<ArrayList<AppEvent>>(); 
	
	private ArrayList<HashMap<Key, IMasterControl>> controlMapList = new ArrayList<HashMap<Key, IMasterControl>>(); //do not init
	
	
	private int activeQueue = 0;
	private int numQueues = 2;
	
	public EventManager()
	{
		for (int i=0; i<numQueues; i++)
		{eventQueue.add(new ArrayList<AppEvent>());}	
		
		logger.log(Level.INFO, "EventManager: EventManager initialized");		
	}
	
	public void addControlMapReference(HashMap<Key, IMasterControl> controlMap) {
		controlMapList.add(controlMap);
	}
	
	public boolean removeControlMapReference(HashMap<Key, IMasterControl> controlMap) {
		return controlMapList.remove(controlMap);
	}
	
	public boolean addListener(IEventListener listener, Set<String> types) {
		for (String type: types) {
			this.addListener(listener, type);
		}
		
		//logger.log(Level.INFO, "EventManager: addAllListeners() Total events added= " + types.size());
		
		return true;
	}
	
	public boolean addListener(IEventListener listener, String eventType) {
		//add event to set (returns false if already present)
		eventNameSet.add(eventType);
				
		//add to map if event type not already contained
		if (!eventListenerMap.containsKey(eventType))
		{
			int sizeTable = eventListenerTable.size();
			//create new list and add to existing table and map
			eventListenerTable.add(sizeTable, new ArrayList<IEventListener>()); //add a new, empty list to table
			eventListenerTable.get(sizeTable).add(listener); //add listener to the new, empty list in the table
			
			eventListenerMap.put(eventType, new ArrayList<IEventListener>()); //add new, empty list to map
			eventListenerMap.get(eventType).add(listener); //add listener to the new, empty list in the map
		}
		else { //add to list if event type already contained but specific listener
				//does not exist
			
			if (!eventListenerMap.get(eventType).contains(listener))
			{
				eventListenerMap.get(eventType).add(listener);				
			}
		}		
		return true;
	}


	public boolean delListener(IEventListener listener, String eventName) {
		//remove listener from list for just one event
		return eventListenerMap.get(eventName).remove(listener);			
	}
	
	public boolean delListener(IEventListener listener, Set<String> eventNames) {
		//remove listener from list for multiple events
		for (String eventName: eventNames) {
			eventListenerMap.get(eventName).remove(listener);
		}
		
		return true;
	}
	
	public boolean delAllListeners() {
		//clear all listeners
		eventListenerMap.clear();
		return eventListenerMap.isEmpty();
	}

	//Handle events in current queue, with some time constraint
	public boolean procEvents(long maxMillis) {
		int queueToProcess = activeQueue;
		boolean procAll = true;
		
		//ensure new queue is empty
		activeQueue = (activeQueue+1) % numQueues;
		eventQueue.get(activeQueue).clear();
		//System.out.println("Eventqueue size 1= " + eventQueue.get(queueToProcess).size());
		long startTime = System.nanoTime();
		while (!eventQueue.get(queueToProcess).isEmpty())
		{
			//grab first event from queue
			AppEvent event = eventQueue.get(queueToProcess).get(0);
			
			String eventName = event.getEventName();
			
			if (eventListenerMap.containsKey(eventName))
			{
				if (event.getTarget() != null) {
					//handle event for a specific target
					//System.out.println("Specific Target");
					for (HashMap<Key, IMasterControl> map: controlMapList) {
						if (map.containsKey(event.getTarget())) {
							map.get(event.getTarget()).handleEvent(event);
							logger.log(Level.INFO, "Handle targeted event, " + event.getEventName() + " for " + event.getTarget().toString());
						}						
					}
				}
				else {
					//handle event for all listeners in this list	
					//System.out.println("General Target");
					for (int i = 0; i < eventListenerMap.get(eventName).size(); i++) {
						//System.out.println("Handling event: " + eventName);
						//System.out.println("eventListenerMap: " + eventListenerMap.get(eventName).size());
						logger.log(Level.INFO, "Handle broadcast event, " + event.getEventName() + " for object " + i);
						eventListenerMap.get(eventName).get(i).handleEvent(event);
					}				
				}				
			}
			
			//Delete first event from front of queue
			eventQueue.get(queueToProcess).remove(0);
			//Check time elapsed
			long timeElapsed = (System.nanoTime() - startTime)/1000;  //convert from ns to ms
			
			//If time elapsed too great, transfer remaining queued events to beginning of other queue and exit loop
			//else continue loop			
			if (timeElapsed > maxMillis)
			{
				eventQueue.get(activeQueue).addAll(0, eventQueue.get(queueToProcess));
				procAll = false;
				break;
			}
		}
		
		return procAll;
	}

	//Add event to queue to await handling
	public boolean queueEvent(AppEvent event) {
		if (validateEventType(event.getEventName())){
			logger.log(Level.INFO, "EventManager: queueEvent(); queue add SUCCEEDED " + event.getEventName());			
			return eventQueue.get(activeQueue).add(event);
		}
		else {
			logger.log(Level.INFO, "EventManager: queueEvent() queue add FAILED " + event.getEventName());
			return false;
		}
	}

	//Execute given event immediately, without queueing
	public boolean trigger(AppEvent event) {
		
		if (!validateEventType(event.getEventName()))
			return false;
		
		if (eventListenerMap.containsKey(event.getEventName()))
		{
			logger.log(Level.INFO, "EventManager: triggerEvent() SUCCESS " + event.getEventName());
			//handle events for all listeners in this list				
			for (Iterator<IEventListener> it = eventListenerMap.get(event.getEventName()).iterator(); it.hasNext();) {
				it.next().handleEvent(event);					
			}
			
			return true;
		}
		else
			logger.log(Level.INFO, "EventManager: triggerEvent() NO LISTENERS " + event.getEventName());
			return false;
	}
	
	//Remove event from queue (or all such events from queue)
	public boolean abortEvent(AppEvent event, boolean allOfType) {
		if (!validateEventType(event.getEventName()))
			return false;
		
		if (allOfType)
		{
			boolean abortSuccessful = false; //single event abort
			boolean allAbortSuccessful = true; //tracks all event aborts
			//Iterate through event queue
			for (Iterator<AppEvent> it = eventQueue.get(activeQueue).iterator(); it.hasNext();)
			{
				//If next event is of same type as target event, remove it
				if (it.next().getEventName().equals(event.getEventName()))
				{
					abortSuccessful = eventQueue.get(activeQueue).remove(it.next());
				}				
				if (!abortSuccessful)
					allAbortSuccessful = false;
			}
			return allAbortSuccessful;
		}
		else //just remove the target event
			return eventQueue.get(activeQueue).remove(event);		
		
		
	}

	//Check if event type is registered with the manager
	public boolean validateEventType(String eventName) {
		
		/*//empty eventType
		if (eventType.equals(null))
			return false;*/
		
		if (eventNameSet.contains(eventName))			
			return true;
		else
			return false;
	}
	
	//Adds events from scripts
	//NOTE:  INCOMPLETE
	/*public void addScriptEventType(String eventType)
	{
		eventTypeSet.add(eventType);
	}*/
	
	//Add event from code source that cannot be created via script
	//NOTE:  INCOMPLETE
//	public void addCodeEventType(String eventName)
//	{
//		eventNameSet.add(eventName);		
//	}
	
	//return a list of listeners associated with given EventType
	public ArrayList<IEventListener> getListenerList(String eventName)
	{
		return eventListenerMap.get(eventName);		
	}
	
	//return list of all registered event types
	public Set<String> getTypeList()
	{
		return eventNameSet;		
	}
}
